﻿/*
 *   Copyright 2011 Peter Crossley
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 *  ----------------------------------------------------------------------------
 *  File:       CloudManager.cs
 *  Author:     Peter Crossley
 *  ----------------------------------------------------------------------------
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using UEC.Modules;
using UEC.Modules.Basic;
using Microsoft.Win32;
using System.Net;
using System.IO.Ports;
using System.Management;
using System.Collections;
using System.Runtime.InteropServices;
using UEC.Modules.Configuration;
using System.Xml.Serialization;
using UEC.Modules.Sysprep;
using System.Windows.Forms; 


namespace UEC
{
    public class CloudManager
    {
        const string COMMON_SERVICE = "Common";

        private System.Diagnostics.EventLog eventLog;
        private String LOG_SOURCE = "Cloud Service Manager";
        private String LOG_NAME = "Cloud Manager Log";

        public delegate void LoadSettingsHandler(object sender, LoadEventArgs e);
        public delegate void SettingsChangedHandler(object sender, EventArgs e);
        public delegate void SaveSettingsHandler(object sender, EventArgs e);
        public delegate void CloudServiceHandler(object sender, CloudServiceEventArgs e);
        public delegate void CloudManagerHandler(object sender, EventArgs e);

        public event SettingsChangedHandler SettingsChangeEvent;
        public event SaveSettingsHandler SaveSettingsEvent;
        public event LoadSettingsHandler LoadSettingsEvent;
        public event CloudServiceHandler CloudServiceEvent;
        public event CloudManagerHandler CloudManagerEvent;


        private IList<ICloudModule> uecServices = new List<ICloudModule>();
        private String ApplicationPath;
        private String ApplicationExecPath;
        private CloudManagerSettings _cli;
        
        private Microsoft.Win32.RegistryKey registryKey;

        private bool _reboot = false;
        private bool _userMode = false;

        private CloudInit _cloudInitData = null;
        private ServiceContext _serviceContext = null;

        private IDictionary<string, object> _properties = null;

        public bool Debug = false;

        public CloudManager(string startupPath, string execPath, Microsoft.Win32.RegistryKey registryKey, CloudManagerSettings cli)
        {
            _cli = cli;
            if (!System.Diagnostics.EventLog.SourceExists(LOG_SOURCE))
                System.Diagnostics.EventLog.CreateEventSource(LOG_SOURCE, LOG_NAME);

            eventLog = new System.Diagnostics.EventLog();
            eventLog.Source = LOG_SOURCE;
            eventLog.Log = LOG_NAME;

            _properties = new Dictionary<string, object>();

            //what is the location of the application
            this.ApplicationPath = startupPath;
            this.ApplicationExecPath = execPath;
            this.registryKey = registryKey;
            
            _serviceContext = new ServiceContext(this);
            LoadSettingsEvent += new LoadSettingsHandler(this.CloudManager_LoadSettingsEvent);
            LoadPlugins();
        }

        public string ApplicationDirectory
        {
            get
            {
                return this.ApplicationPath;
            }
        }

        
        public IList<ICloudModule> Services
        {
            get { return uecServices; }
        }

        public bool IsUserMode
        {
            set
            {
                if (value && !_userMode)
                {
                    _userMode = true;
                }
            }
            get { return _userMode; }
        }

        public bool Reboot
        {
            set { 
                if (value && !_reboot) {
                    _reboot = true;
                    CloudServiceEvent += new CloudManager.CloudServiceHandler(SystemRebootHandler);
                }
            }
            get { return _reboot; }
        }

        private void SystemRebootHandler(object sender, CloudManager.CloudServiceEventArgs e)
        {
            if (e.CloudServiceState == CloudServiceStates.SYSTEM_REBOOT)
            {
                //do somthing
                LogEvent("CloudManager: (" + e.CloudServiceState.ToString() + ")");
                int reboot = 0;
                bool ok = int.TryParse(""+e.Properties["rebootTime"], out reboot);
                if (ok)
                {
                    RebootSystem(reboot);
                }
                else
                {
                    RebootSystem(0);
                }
            }
            
        }

        public ServiceContext ServiceContext
        {
            get
            {
                return _serviceContext;
            }
        }

        /** METADATA **/
        public string GetMetaDataValue(string path)
        {
            try
            {
                string metadataUrl = (string)GetValue(BasicService.BASIC_SETTINGS, "MetaDataUrl", Properties.Settings.Default.MetaDataUrl);
                Uri serviceUri = new Uri(metadataUrl + path);
                WebClient downloader = new WebClient();
                string value = downloader.DownloadString(serviceUri);
                if (!string.IsNullOrEmpty(value))
                {
                    value = value.Trim();
                }
                return value;
            }
            catch (Exception e)
            {
                LogEvent("Unable to contact meta-data service [path: " + path + "] :\n" + e.ToString(), EventLogEntryType.Error, true);
                return null;
            }
        }

        /** USERDATA **/
        public string GetUserData()
        {
            string userdataFile = ApplicationPath + Path.DirectorySeparatorChar + "user-data.xml";
            try
            {
                if (!File.Exists(userdataFile)) {
                    string userDataUrl = (string)GetValue(BasicService.BASIC_SETTINGS, "UserDataUrl", Properties.Settings.Default.UserDataUrl);
                    Uri serviceUri = new Uri(userDataUrl);
                    WebClient downloader = new WebClient();
                    StreamWriter writer = null;
                    bool done = false;
                    for (int i = 0; (i < 6 || !done); i++) {
                        try
                        {
                            string userdata = downloader.DownloadString(serviceUri);
                            if (String.IsNullOrEmpty(userdata))
                            {
                                //dont have user data
                                done = true;
                                return null;
                            }
                            writer = File.CreateText(userdataFile);
                            writer.Write(userdata);
                            done = true;
                        }
                        catch (WebException we)
                        {
                            if (_cli.AppManager)
                            {
                                throw we;
                            }

                            WriteConsole("Unable to contact user-data service, retry (" + i+1 + "), sleeping 10 seconds.");
                            done = false;
                            if (i < 5)
                            {
                                Thread.Sleep(10000); // sleep for 10 seconds to make sure we have user-data
                            }
                            else
                            {
                                //we can not get the user-data in the time allowed, raise the exception.
                                throw we;
                            }
                        }
                        finally {
                            if (writer != null) {
                                writer.Close();
                            }
                        }
                    }
                } 
                return File.ReadAllText(userdataFile);
            }
            catch (Exception e)
            {
                LogEvent("Unable to contact user-data service:\n" + e.ToString(), EventLogEntryType.Error, true);
                try
                {
                    File.Delete(userdataFile);
                }
                catch (Exception e1)
                {
                    LogEvent("Unable to delete local cached user-data on load from metadata service:\n" + e1.ToString(), EventLogEntryType.Error);    
                }
                return null;
            }
        }

        private void CloudManager_LoadSettingsEvent(object sender, LoadEventArgs e)
        {
            string userdataFile = ApplicationPath + Path.DirectorySeparatorChar + "user-data.xml";
            try
            {
                File.Delete(userdataFile);
            }
            catch (Exception e1)
            {
                //do nothing
                LogEvent("Unable to delete local cached user-data on load:\n" + e1.ToString(), EventLogEntryType.Error);    
            }
        }

        public CloudInit CloudInit
        {
            get
            {
                if (_cloudInitData == null)
                {
                    try
                    {
                        _cloudInitData = ConfigurationService.DeserializeCloudInitFromXML(GetUserData());
                    }
                    catch (Exception e)
                    {
                        LogEvent("Unable to load cloud init data [" + e.Message + "]\n" + e.StackTrace, EventLogEntryType.Error, true);
                    }
                }
                return _cloudInitData;
            }
        }


        public void LoadUserDataSettings()
        {
            CloudInit cloud = CloudInit;
            if (cloud != null && cloud.Settings != null) { 
                //look at each settings section and update registry data
                foreach (Config c in cloud.Settings)
                {
                    this.SetValue(c.Section, c.Name, c.Value);
                }
            }
        }


        public IDictionary<string, object> SystemProperties
        {
            get
            {
                return _properties;
            }
        }


        /** CONFIGURATION **/

        public void DeleteKeyStore(string type)
        {
            string[] subKeys = registryKey.GetSubKeyNames();
            if (subKeys.Contains(type))
            {
                registryKey.DeleteSubKeyTree(type);
            }
        }

        public void DeleteKey(string type, string key)
        {
            string[] subKeys = registryKey.GetSubKeyNames();
            if (subKeys.Contains(type))
            {
                RegistryKey rkey = registryKey.OpenSubKey(type, true);
                if (rkey != null)
                {
                    rkey.DeleteValue(key, false);
                }
            }
        }

        public bool GetValueAsBoolean(string type, string key, bool defaultValue)
        {
            object val = GetValue(type, key, defaultValue);
            try
            {
                if (val != null)
                {
                    return Boolean.Parse(val.ToString());
                }
                return defaultValue;
            }
            catch (Exception e)
            {
                LogEvent("Unable to get configuration value, using default value: " + defaultValue + "\n" +e.ToString(), EventLogEntryType.Warning);
                return defaultValue;
            }
        }

        public object GetValue(string type, string key, object defaultValue)
        {
            RegistryKey rkey = registryKey.OpenSubKey(type);
            if (rkey != null)
            {
                return rkey.GetValue(key, defaultValue);
            }
            return defaultValue;
        }

        public void SetValue(string type, string key, object value)
        {
            SetValue(type, key, value, RegistryValueKind.String);
        }

        public void SetValue(string type, string key, object value, RegistryValueKind keyType)
        {
            RegistryKey rkey = registryKey.CreateSubKey(type);
            if (value == null)
            {
                rkey.DeleteValue(key);
            }
            else
            {
                rkey.SetValue(key, value, keyType);
            }
        }

        public string[] GetTypes()
        {
            return registryKey.GetSubKeyNames();
        }

        public string[] GetValueNames(string type) 
        {
            RegistryKey rkey = registryKey.OpenSubKey(type);
            if (rkey != null) {
                return rkey.GetValueNames();
            }
            return null;
        }


        /** LOGGING **/

        public void LogEvent(string message)
        {
            LogEvent(message, System.Diagnostics.EventLogEntryType.Information, false);
        }
        
        public void LogEvent(string message, System.Diagnostics.EventLogEntryType logType)
        {
            LogEvent(message, logType, false);
        }

        public void LogEvent(string message, System.Diagnostics.EventLogEntryType logType, bool console)
        {
            eventLog.WriteEntry(message, logType);
            if (console)
            {
                WriteConsole(message);
            }
            if (Debug && !_cli.Service)
            {
                MessageBox.Show(message, Properties.Resources.SystemMessageTitle);
            }
        }

        public void WriteConsole(string message)
        {
            WriteConsole(message, true);
        }

        public void WriteConsole(string message, bool timestamp)
        {
            SerialPort _serialPort = new SerialPort();
            // Create a new SerialPort object with default settings.
            _serialPort = new SerialPort();

            // Allow the user to set the appropriate properties.
            _serialPort.PortName = (string)GetValue("CloudSettings", "ComPort", "COM1");

            // Set the read/write timeouts
            _serialPort.ReadTimeout = 500;
            _serialPort.WriteTimeout = 500;

            try
            {
                _serialPort.Open();
                _serialPort.WriteLine((timestamp ? "[" + DateTime.Now + "] " : "") + message);
            }
            catch (Exception e)
            {
                eventLog.WriteEntry("Error writting to console: \n" + e.ToString(), EventLogEntryType.Warning);
            }
            finally
            {
                if (_serialPort != null)
                {
                    _serialPort.Close();
                }
            }
        }


        public void WriteConsole(Stream from)
        {
            SerialPort _serialPort = new SerialPort();
            // Create a new SerialPort object with default settings.
            _serialPort = new SerialPort();

            // Allow the user to set the appropriate properties.
            _serialPort.PortName = (string)GetValue("CloudSettings", "ComPort", "COM1");

            // Set the read/write timeouts
            _serialPort.ReadTimeout = 500;
            _serialPort.WriteTimeout = 500;

            try
            {
                _serialPort.Open();
                _serialPort.WriteLine("[" + DateTime.Now + "] - ");
                for (int a = from.ReadByte(); a != -1; a = from.ReadByte())
                    _serialPort.BaseStream.WriteByte((byte)a);

            }
            catch (Exception e)
            {
                eventLog.WriteEntry("Error writting to console: \n" + e.ToString(), EventLogEntryType.Warning);
            }
            finally
            {
                if (_serialPort != null)
                {
                    _serialPort.Close();
                }
            }
        }


        /** EVENTS **/
        
        public void SettingsChanged(object sender, EventArgs e) {
            SettingsChangeEvent(this, new EventArgs());
        }

        public void OnLoadSettings(bool reset)
        {
            if (reset)
            {
                LogEvent("Cloud Services Settings were reset to orginal values.", System.Diagnostics.EventLogEntryType.Information);
            }
            LoadSettingsEvent(this, new LoadEventArgs(reset));
        }

        public void OnSaveSettings()
        {
            LogEvent("Cloud Services Settings were saved and updated, some setting my require a reboot.", System.Diagnostics.EventLogEntryType.Information);
            SaveSettingsEvent(this, new EventArgs());
        }

        public void OnCloudManagerClose()
        {
            CloudManagerEvent(this, new CloudManagerEventArgs(CloudManagerStates.CLOSE));
        }

        public void OnCloudServiceStart()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.START));
        }

        public void OnCloudServiceStop()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.STOP));
        }

        public void OnCloudServiceRun()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.RUN));
        }

        public void OnCloudServiceSysprep()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.SYSPREP));
        }
        public void OnCloudServiceSysprepAudit()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.SYSPREP_AUDIT));
        }
        
        public void OnCloudServiceSetProperties()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.PROPERTIES));
        }

        public void OnCloudServiceInstall()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.INSTALLING));
        }

        public void OnCloudServiceUnInstall()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.UNINSTALLING));
        }

        public void OnCloudServiceChange()
        {
            CloudServiceEvent(this, new CloudServiceEventArgs(CloudServiceStates.CHANGED));
        }

        public void OnCloudServiceSystemReboot()
        {
            OnCloudServiceSystemReboot(0);
        }

        public void OnCloudServiceSystemReboot(int timeToReboot)
        {
            CloudServiceEventArgs e = new CloudServiceEventArgs(CloudServiceStates.SYSTEM_REBOOT);
            e.Properties.Add("rebootTime", timeToReboot);
            CloudServiceEvent(this, e);
        }


        public class LoadEventArgs : EventArgs
        {
            private bool reset;
            public LoadEventArgs(bool reset)
            {
                this.reset = reset;
            }

            public bool Reset
            {
                get { return reset; }
            }

        }

        public class CloudManagerEventArgs : EventArgs
        {
            private CloudManagerStates state;

            public CloudManagerEventArgs(CloudManagerStates state)
            {
                this.state = state;
            }

            public CloudManagerStates CloudManagerState
            {
                get { return state; }
            }

        }

        public enum CloudManagerStates
        {
            CLOSE
        }

        public class CloudServiceEventArgs : EventArgs
        {
            private CloudServiceStates state;
            private IDictionary properties = new Hashtable();

            public CloudServiceEventArgs(CloudServiceStates state)
            {
                this.state = state;
            }

            public CloudServiceStates CloudServiceState
            {
                get { return state; }
            }

            public IDictionary Properties
            {
                get { return properties; }
            }


        }

        public enum CloudServiceStates
        {
            INSTALLING,
            UNINSTALLING,
            START,
            STOP,
            CHANGED,
            RUN,
            SYSPREP,
            SYSPREP_AUDIT,
            PROPERTIES,
            SYSTEM_REBOOT

        }

        //Supporting manager functions

        private void LoadPlugins()
        {
            //generic service 
            RegisterService(new BasicService(this));
            RegisterService(new SysprepService(this));
            RegisterService(new ConfigurationService(this));

            try
            {
                string libDir = ApplicationPath + Path.DirectorySeparatorChar + "lib";
                if (Directory.Exists(libDir)) {
                    String[] libraries = Directory.GetFiles(libDir, "*.dll");
                    foreach (string lib in libraries)
                    {
                        Type[] types = Assembly.LoadFile(ApplicationPath + Path.DirectorySeparatorChar + "lib" + Path.DirectorySeparatorChar + lib).GetTypes();
                        foreach (Type type in types)
                        {
                            if (type.IsInstanceOfType(typeof(ICloudModule)))
                            {
                                RegisterService((ICloudModule)Activator.CreateInstance(type, this));
                            }
                            else
                            {
                                this.LogEvent("Cloud Service could not load dynamic library: " + lib, System.Diagnostics.EventLogEntryType.Warning);
                            }
                        }
                    }
                } else {
                    this.LogEvent("Cloud Service is not using external plugins.");
                }
            }
            catch (Exception e)
            {
                LogEvent("Unable to load plugin libraries.\n" + e.ToString(), EventLogEntryType.Warning);
            }
        }

        private void RegisterService(ICloudModule service)
        {
            uecServices.Add(service);
        }

        private void RebootSystem(int timeInSeconds)
        {
            this.LogEvent("System reboot in " + timeInSeconds + " seconds.", EventLogEntryType.Information,true);
            Process process = new Process();
            process.StartInfo.UseShellExecute = true;
            process.StartInfo.WorkingDirectory = Environment.SpecialFolder.System.ToString();
            process.StartInfo.FileName = "shutdown.exe";
            process.StartInfo.Arguments = "-r -f -t " + timeInSeconds;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            process.Start();
        }

        public string Mode { 
            get { 
                return (string) GetValue(COMMON_SERVICE, "Mode",null);
            }
            set {
                SetValue(COMMON_SERVICE, "Mode", value);
            }
        }
    }
}
